package xfix.fitness.xbi;

import edu.gatech.xpert.XpertMain;
import xfix.Constants;
import xfix.RootCause;
import xfix.RootCauseList;
import xfix.Util;
import xfix.WebDriverSingleton;
import xfix.XFixConstants;
import xfix.XbiElementRelationship;
import xfix.XbiMainIterator;
import xfix.input.xbi.ReadXpertOutput;
import xfix.input.xbi.XpertXbi;

import java.awt.Rectangle;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class XbiFitnessFunction {
    private static int localFitnessCalls;
    private static double localFitnessTimeInSec;
    private static int globalFitnessCalls;
    private static double globalFitnessTimeInSec;
    private static String jsonRef;
    private static Map<String, Rectangle> boundingBoxesRef;
    private Map<String, Rectangle> boundingBoxesTest;
    private static Map<String, XbiElementRelationship> elementRelationshipsRef;
    private static Map<String, XbiElementRelationship> elementRelationshipsTest;

    private static HtmlDomTree domTreeRef;
    private static HtmlDomTree domTreeTest;

    private XpertMain xm;
    public XbiFitnessFunction() {
        if (elementNeighborsCache == null) {
            elementNeighborsCache = new HashMap<>();
        }
        if(xm == null){
            xm = new XpertMain();
        }
    }

    private static Map<String, Set<String>> elementNeighborsCache;

    public static void setLocalFitnessCalls(int localFitnessCalls) {
        XbiFitnessFunction.localFitnessCalls = localFitnessCalls;
    }

    public static void setLocalFitnessTimeInSec(double localFitnessTimeInSec) {
        XbiFitnessFunction.localFitnessTimeInSec = localFitnessTimeInSec;
    }

    public static void setGlobalFitnessCalls(int globalFitnessCalls) {
        XbiFitnessFunction.globalFitnessCalls = globalFitnessCalls;
    }

    public static void setGlobalFitnessTimeInSec(double globalFitnessTimeInSec) {
        XbiFitnessFunction.globalFitnessTimeInSec = globalFitnessTimeInSec;
    }

    public static int getLocalFitnessCalls() {
        return localFitnessCalls;
    }

    public static double getLocalFitnessTimeInSec() {
        return localFitnessTimeInSec;
    }

    public static int getGlobalFitnessCalls() {
        return globalFitnessCalls;
    }

    public static double getGlobalFitnessTimeInSec() {
        return globalFitnessTimeInSec;
    }

    public static String getJsonRef() {
        return jsonRef;
    }

    public static void setJsonRef(String jsonRef) {

        XbiFitnessFunction.jsonRef = jsonRef;
    }

    public static Map<String, Rectangle> getBoundingBoxesRef() {
        return boundingBoxesRef;
    }

    public static void setBoundingBoxesRef(Map<String, Rectangle> boundingBoxesRef) {
        XbiFitnessFunction.boundingBoxesRef = boundingBoxesRef;
    }

    public Map<String, Rectangle> getBoundingBoxesTest() {
        return boundingBoxesTest;
    }

    public void setBoundingBoxesTest(Map<String, Rectangle> boundingBoxesTest) {
        this.boundingBoxesTest = boundingBoxesTest;
    }

    public static Map<String, XbiElementRelationship> getElementRelationshipsRef() {
        return elementRelationshipsRef;
    }

    public static void setElementRelationshipsRef(Map<String, XbiElementRelationship> elementRelationshipsRef) {
        XbiFitnessFunction.elementRelationshipsRef = elementRelationshipsRef;
    }

    public static Map<String, XbiElementRelationship> getElementRelationshipsTest() {
        return elementRelationshipsTest;
    }

    public static void setElementRelationshipsTest(Map<String, XbiElementRelationship> elementRelationshipsTest) {
        XbiFitnessFunction.elementRelationshipsTest = elementRelationshipsTest;
    }

    public static HtmlDomTree getDomTreeRef() {
        return domTreeRef;
    }

    public static void setDomTreeRef(HtmlDomTree domTreeRef) {
        XbiFitnessFunction.domTreeRef = domTreeRef;
    }

    public static HtmlDomTree getDomTreeTest() {
        return domTreeTest;
    }

    public static void setDomTreeTest(HtmlDomTree domTreeTest) {
        XbiFitnessFunction.domTreeTest = domTreeTest;
    }

    //计算块内的XBI：负责reload页面
    public Set<String> getPartXbi(RootCauseList chromosome,RootCause gene){
        //没关的话不用load
//        WebDriverSingleton.loadPage(XFixConstants.getOraclePageFullPath(), Constants.REFERENCE_BROWSER);

        WebDriverSingleton.loadPage(XFixConstants.getTestPageFullPath(), Constants.TEST_BROWSER);
        Util.applyNewValues(chromosome, WebDriverSingleton.getDriver(Constants.TEST_BROWSER));
//        Util.applyNewValue("float",gene.getXpath(),chromosome.getGene(gene.getXpath()).getValue("float"),
//                WebDriverSingleton.getDriver(Constants.TEST_BROWSER));
        if (jsonRef == null || jsonRef.isEmpty()) {
            jsonRef = XbiUtil.getDOMJson(Constants.REFERENCE_BROWSER);
        }
        String jsonTest = XbiUtil.getDOMJson(Constants.TEST_BROWSER);

        boundingBoxesTest = XbiUtil.getBoundingBoxes(jsonTest);


        XbiElementRelationship node = XbiFitnessFunction.getElementRelationshipsRef().get(gene.getXpath());
        Map<String, String> resultsMap;
        if(node == null){
            resultsMap = xm.runXpertWithoutGeneratingReports(jsonRef,jsonTest,"","");
        }else {
            resultsMap = xm.runPartXpertWithGivenNode(node.getParent(),node.getSiblings(),node.getChildren(),
                    gene.getXpath(),jsonRef, jsonTest);
        }

        ReadXpertOutput rxo = new ReadXpertOutput();
        rxo.readInputFromString(resultsMap.get("layout"));
        rxo.readInputFromString(resultsMap.get("content"));

        return rxo.getXbiStrings();
    }
    //计算localFitness
    public double[] getFitnessScoreLocal(RootCauseList chromosome, RootCause gene) {
        WebDriverSingleton.loadPage(XFixConstants.getTestPageFullPath(), Constants.TEST_BROWSER);
        Util.applyNewValues(chromosome, WebDriverSingleton.getDriver(Constants.TEST_BROWSER));
        if (jsonRef == null || jsonRef.isEmpty()) {
            jsonRef = XbiUtil.getDOMJson(Constants.REFERENCE_BROWSER);
        }
        String jsonTest = XbiUtil.getDOMJson(Constants.TEST_BROWSER);

        boundingBoxesTest = XbiUtil.getBoundingBoxes(jsonTest);

        long startTime = System.nanoTime();

        double[] fitnessScore = calculateFitnessScore(chromosome.getXpertXbi());

        long endTime = System.nanoTime();
        localFitnessTimeInSec = localFitnessTimeInSec + Util.convertNanosecondsToSeconds((endTime - startTime));
        localFitnessCalls++;

        return fitnessScore;
    }

    public double getFitnessScoreGlobal(RootCauseList chromosome) {
        double fitnessScore = 0.0;
        long startTime = System.nanoTime();

        // run xpert

        if (jsonRef == null || jsonRef.isEmpty()) {
            jsonRef = XbiUtil.getDOMJson(Constants.REFERENCE_BROWSER);

        }

        // get dom info from test browser
        WebDriverSingleton.loadPage(XFixConstants.getTestPageFullPath(), Constants.TEST_BROWSER);
        Util.applyNewValues(chromosome, WebDriverSingleton.getDriver(Constants.TEST_BROWSER));

        String jsonTest = XbiUtil.getDOMJson(Constants.TEST_BROWSER);

        Map<String, String> resultsMap = xm.runXpertWithoutGeneratingReports(jsonRef, jsonTest, "", "");

        // read input from XPERT's reported file
        ReadXpertOutput rxo = new ReadXpertOutput();
        rxo.readInputFromString(resultsMap.get("layout"));
        rxo.readInputFromString(resultsMap.get("content"));
        fitnessScore = rxo.getTotalNumberOfXBIsReported();
        System.out.println("Global fitness score: XBIs by XPERT (size = " + rxo.getXbiStrings().size() + ")");
        for (String xbi : rxo.getXbiStrings()) {
            System.out.println(xbi);
        }

        long endTime = System.nanoTime();
        globalFitnessCalls++;
        globalFitnessTimeInSec = globalFitnessTimeInSec + Util.convertNanosecondsToSeconds((endTime - startTime));

        return fitnessScore;
    }

    //    public double getFitnessScoreBestCombination(RootCauseList realChromosome, List<OptimalRootCause> candidateRootCausesChromosome, String binaryChromosome) {
//        // update chromosome with values from binary chromosome
//        RootCauseList tempChromosome = realChromosome.copy();
//        int i = 0;
//        for (OptimalRootCause og : candidateRootCausesChromosome) {
//            if (binaryChromosome.charAt(i) == '1') {
//                // set value in chromosome
//                RootCause tempGene = tempChromosome.getGene(og.getXpath());
//                tempGene.updateValue(og.getProp(), og.getVal());
//            }
//            i++;
//        }
//        return getFitnessScoreGlobal(tempChromosome);
//    }
    private double[] absolutePositionRefTestDOMNeighborsMethod(String eRef1,String eRef2, String eTest1,String eTest2) {
        Rectangle rectERef1 = boundingBoxesRef.get(eRef1);
        Rectangle rectERef2 = boundingBoxesRef.get(eRef2);
        Rectangle rectETest1 = boundingBoxesTest.get(eTest1);
        Rectangle rectETest2 = boundingBoxesTest.get(eTest2);

        double[] fitnessScore = new double[Constants.FITNESSFUNCTION_DIMENSION];
        CalculateDistance cal = new CalculateDistance(rectERef1.x, rectERef2.x, rectETest1.x, rectETest2.x,rectERef2.width,rectETest2.width);
        fitnessScore[0] = getDistanceBetweenPoints(cal);

        cal = new CalculateDistance(rectERef1.y, rectERef2.y, rectETest1.y,
                rectETest2.y,rectERef2.height,rectETest2.height);
        fitnessScore[1] = getDistanceBetweenPoints(cal);

        cal = new CalculateDistance(rectERef1.x+rectERef1.width,rectERef2.x+rectERef2.width, rectETest1.x
                +rectETest1.width,rectETest2.x+rectETest2.width,rectERef2.width,rectETest2.width);
        fitnessScore[2] = getDistanceBetweenPoints(cal);

        cal = new CalculateDistance(rectERef1.y+rectERef1.height,rectERef2.y+rectERef2.height,rectETest1.y
                +rectETest1.height,rectETest2.y+rectETest2.height,rectERef2.height,rectETest2.height);
        fitnessScore[3] = getDistanceBetweenPoints(cal);
        return fitnessScore;
    }
    class CalculateDistance{
        int x1,x2,y1,y2;
        int denominator1,denominator2;
        CalculateDistance(int x1, int x2, int y1, int y2,int denominator1,int denominator2){
            this.x1 = x1;
            this.x2 = x2;
            this.y1 = y1;
            this.y2 = y2;
            this.denominator1 = denominator1;
            this.denominator2 = denominator2;
        }
    }
    private double getDistanceBetweenPoints(CalculateDistance cal) {
//        return Math.sqrt(Math.pow((x2 - x1), 2) + Math.pow((y2 - y1), 2));
        return ((cal.x1 - cal.x2 + 0.0)/cal.denominator1 - (cal.y1-cal.y2+0.0)/cal.denominator2) * 10;
//        return (cal.x1 - cal.x2 + 0.0)- (cal.y1-cal.y2+0.0);
    }

    private double[] calculateFitnessScore(XpertXbi xpertXbi) {
        if(xpertXbi.getE1Test()!=null && xpertXbi.getE2Test()!=null){
            return absolutePositionRefTestDOMNeighborsMethod(xpertXbi.getE1Ref(),xpertXbi.getE2Ref(),
                    xpertXbi.getE1Test(),xpertXbi.getE2Test());
        }
        return null; //missing sibling？？？
        // get matching element from reference browser
//        String eRef = XbiUtil.getMatchedNodeXpaths().get(eTest);

//        return absolutePositionRefTestDOMNeighborsMethod(eRef, eTest);
    }
}
